package secondary.vo;


import jade.content.Concept;
import jade.content.ContentElement;
import jade.content.lang.Codec;
import jade.content.lang.sl.SLCodec;
import jade.content.onto.Ontology;
import jade.content.onto.basic.Action;
import jade.core.behaviours.CyclicBehaviour;
import jade.lang.acl.ACLMessage;
import jade.util.leap.ArrayList;
import ontology.VOOntology;
import ontology.actions.general.AddNewTime;
import ontology.actions.general.GetSimilarities;
import ontology.actions.general.ReturnSimilarities;
import ontology.concepts.vo.VOObligation;
import ontology.concepts.vo.Situation;
import ontology.concepts.vo.VOCase;
import ontology.concepts.vo.VONorm;
import uk.ac.shef.wit.simmetrics.similaritymetrics.Jaro;
import classes.UMCourtAgent;

public class NearestNeighbourVO extends UMCourtAgent 
{
	private Codec codec = new SLCodec();
	private Ontology ontology = VOOntology.getInstance();
	long stime = 0;
	
	protected void setup() 
	{
		System.out.println(getDate()+ ": "+this.getLocalName()+" agent is now running...");
		registerAgent((String)getArguments()[0]);
		
		// Register language and ontology
	    getContentManager().registerLanguage(codec);
	    getContentManager().registerOntology(ontology);
		
		this.addBehaviour(new receiveBehaviour());
	}
	
	class receiveBehaviour extends CyclicBehaviour
	{
		@Override
		public void action() 
		{
			ACLMessage msg = receive();
	        if (msg == null) 
	        { 
	        	block(); 
	        	return; 
	        }
	        
	        try 
			{
	        	ContentElement content = getContentManager().extractContent(msg);
		        Concept action = ((Action)content).getAction();
         		
		        switch (msg.getPerformative()) 
		        {
		        case (ACLMessage.REQUEST):
			        if (action instanceof GetSimilarities)
			        {
			        	
			        	stime = System.currentTimeMillis();
			        	content = getContentManager().extractContent(msg);
			            GetSimilarities<Integer> gs = (GetSimilarities<Integer>) ((Action)content).getAction();
			            myprintln("Received request for similarities: "+msg.getConversationId()+" size: "+gs.getCases().size());
			            
			            ReturnSimilarities<Integer> rs = handleSimilaritiesRequest(gs);
			            
			            ACLMessage reply = new ACLMessage(ACLMessage.INFORM);
			        	reply.setLanguage(codec.getName());
			        	reply.setOntology(ontology.getName());
			        	reply.setConversationId(msg.getConversationId());
						
						try 
						{
							getContentManager().fillContent(reply, new Action(msg.getSender(), rs));
							reply.addReceiver(msg.getSender());
							send(reply);
							myprintln("Sending list of computed similarities to: "+msg.getSender());
						}
						catch (Exception ex) { ex.printStackTrace(); }
			            
			            }
		        }
			}
	        catch(Exception e){e.printStackTrace();}
		}

		private ReturnSimilarities<Integer> handleSimilaritiesRequest(GetSimilarities<Integer> gs) {
			ReturnSimilarities<Integer> rs = new ReturnSimilarities<Integer>();
			
			if(gs.getCases() == null || gs.getNewC()==null)
				return rs;
			
        	VOCase newCase = (VOCase) gs.getNewC();
        	ArrayList cases = gs.getCases();
        	ArrayList caseids = new ArrayList();
        	
        	for (int i=0;i<cases.size();i++)
        		caseids.add(((VOCase)cases.get(i)).getID());
        	
        	String method = gs.getMethod();
        	ArrayList similarities = new ArrayList();
        	
        	if (method.equals("nn"))
        	{
        	
        		myprintln("Using Nearest Neighbour algorithm");
	        	//for each case in cases, compute similarity with newCase
	        	for (int i=0;i<cases.size();i++)
	        		similarities.add(computeSimilarity(newCase, (VOCase)cases.get(i)));
        	}
        	else if (method.equals("cs"))
	        	{
        			myprintln("Using Cosine Similarity algorithm");
	        		csSimilarity(newCase);
	        	}
        		
        	rs.setCaseList(gs.getCases());
        	rs.setComparedCase(gs.getNewC());
        	rs.setSimilarities(similarities);
        	sendNewTime(method, stime);
        	return rs;
		}

		public void sendNewTime(String id, long sTime)
		{
			AddNewTime ant = new AddNewTime();
			ant.setId(id);
			ant.setTime((int) (System.currentTimeMillis()-sTime));
			
			ACLMessage m = new ACLMessage(ACLMessage.REQUEST);
			m.setLanguage(codec.getName());
			m.setOntology(ontology.getName());
			m.setConversationId(""+System.currentTimeMillis());
			
			try 
			{
				getContentManager().fillContent(m, new Action(aids.EVALUATOR, ant));
				m.addReceiver(aids.EVALUATOR);
				send(m);
			}
			catch(Exception e){e.printStackTrace();}
		}
		
		private void csSimilarity(VOCase newCase) {
			myprintln("cosine similarity not yet implemented!!!");
		}
	
		private float computeSimilarity(VOCase nc, VOCase c)
		{
			if (c.getNorm() == null)
			{
				myprintln("WARNING: case "+c.getID()+" lacks norms! *****");
				return 0;
			}
			
			VONorm newCaseNorm = nc.getNorm();
			VONorm olderCaseNorm = c.getNorm();
			
			//Compute norms similarity	
			Jaro j = new Jaro();
			float similarity = j.getSimilarity(newCaseNorm.getName(), olderCaseNorm.getName())*100;
			int comparisons = 1;
			
			for(Situation newSit : newCaseNorm.getAntecedent().getSituation()){
				for(Situation olderSit : olderCaseNorm.getAntecedent().getSituation()){
					comparisons++;
					similarity += computeEquality(newSit, olderSit);
				}
			}
			for( VOObligation newObl: newCaseNorm.getConsequent().getPrescription()){
				for(VOObligation olderObl : olderCaseNorm.getConsequent().getPrescription()){
					comparisons++;
					similarity += computeEquality(newObl, olderObl);
				}
			}
			//Compute violated Obligations similarity (give more weight on this similarity)
			similarity = (similarity / comparisons) / 2;
			float oblSimilarity = computeEquality(nc.getViolation(), c.getViolation())/2;
			return oblSimilarity + similarity;
		}
	
		private float computeEquality(VOObligation newObl, VOObligation olderObl) {
			float similarity = 0;
			Jaro j = new Jaro();
			similarity += j.getSimilarity(newObl.getVoaction().getName(), olderObl.getVoaction().getName());
			similarity += j.getSimilarity(newObl.getVoaction().getArgs(), olderObl.getVoaction().getArgs());
			return similarity*100/2;
		}

		/**
		 * Computes a similarity between two norm situations.
		 * @param newSit
		 * @param olderSit
		 * @return a float between 0 and 100, being 100 a value meaning that the cases are completely equal.
		 */
		private float computeEquality(Situation newSit, Situation olderSit) {
			float similarity = 0;
			Jaro j = new Jaro();
			similarity += j.getSimilarity(newSit.getType(), olderSit.getType());
			similarity += j.getSimilarity(newSit.getVoaction().getName(), olderSit.getVoaction().getName());
			similarity += j.getSimilarity(newSit.getVoaction().getArgs(), olderSit.getVoaction().getArgs());
			return similarity*100/3;
		}
	}
	
}
